package handler

import (
	"context"
	"errors"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"

	"mxshop_srvs/inventory_srv/dao/mysql"
	proto "mxshop_srvs/inventory_srv/proto/gen/v1"
)

func (s *Service) SetInv(_ context.Context, req *proto.GoodsInvInfo) (*emptypb.Empty, error) {
	// 这里需要兼顾修改与新增两种情况,当查询到有对应goods_id的Inventory时,采取update方式,否则采取create方式。
	// 因此在dao层采用Save()函数。Save是一个组合函数。如果保存值不包含主键，它将执行Create，否则将执行Update(包含所有字段)。
	i, err := s.DB.GetInventory(req.GoodsId)
	if err != nil {
		switch {
		case errors.Is(err, gorm.ErrRecordNotFound):
			i.Goods = req.GoodsId // 如果不存在则设置好goods_id
		default:
			return nil, status.Error(codes.Internal, "")
		}
	}

	err = s.DB.SetInventory(i, req.Num)
	if err != nil {
		return nil, status.Error(codes.Internal, "")
	}

	return &emptypb.Empty{}, nil
}

func (s *Service) InvDetail(_ context.Context, req *proto.InvDetailRequest) (*proto.GoodsInvInfo, error) {
	i, err := s.DB.GetInventory(req.GoodsId)
	if err != nil {
		switch {
		case errors.Is(err, gorm.ErrRecordNotFound):
			return nil, status.Error(codes.NotFound, "没有库存信息")
		default:
			return nil, status.Error(codes.Internal, "")
		}
	}

	return &proto.GoodsInvInfo{
		GoodsId: i.Goods,
		Num:     i.Stocks,
	}, nil
}

func (s *Service) Sell(_ context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
	// 扣减库存
	// 一个订单可能有多个商品[商品1:买了10个, 商品2:5个, 商品3:20个]。可能商品1库存足够,商品2库存不足...一个订单要同时成功或者同时失败。
	// 数据库基本的一个应用场景:数据库事务
	// 但是用本地事务还是不够的。在并发情况下可能会出现超卖。在扣减库存时会出现数据不一致的情况。
	// 解决数据不一致的问题需要用-锁。在微服务中可能有多个实例,因此需要用到:分布式锁。(未实现)
	// err := s.DB.Sell(req.GoodsInfo)
	updater := mysql.InventoryUpdate{}
	updater.SetUpdatePolicy(mysql.NewSell(s.DB, s.RDB))
	err := updater.Update(req.GoodsInfo)
	if err != nil {
		switch {
		case errors.Is(err, gorm.ErrRecordNotFound):
			return nil, status.Error(codes.NotFound, "没有库存信息")
		case errors.Is(err, mysql.ErrLowStocks):
			return nil, status.Error(codes.ResourceExhausted, "库存不足")
		default:
			return nil, status.Error(codes.Internal, "扣减失败")
		}
	}

	// rs := s.RDB.NewRedsync()
	// db, _ := mysql.Init()
	// tx := db.Begin()
	// for _, goodInfo := range req.GoodsInfo {
	// 	var inv model.Inventory
	// 	mutex := rs.NewMutex(fmt.Sprintf("goods_%d", goodInfo.GoodsId))
	// 	if err := mutex.Lock(); err != nil {
	// 		return nil, status.Errorf(codes.Internal, "获取redis分布式锁异常")
	// 	}
	//
	// 	if result := db.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
	// 		tx.Rollback() // 回滚之前的操作
	// 		return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
	// 	}
	// 	// 判断库存是否充足
	// 	if inv.Stocks < goodInfo.Num {
	// 		tx.Rollback() // 回滚之前的操作
	// 		return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
	// 	}
	// 	// 扣减， 会出现数据不一致的问题 - 锁，分布式锁
	// 	inv.Stocks -= goodInfo.Num
	// 	// inv.Freeze += goodInfo.Num
	// 	tx.Save(&inv)
	//
	// 	if ok, err := mutex.Unlock(); !ok || err != nil {
	// 		return nil, status.Errorf(codes.Internal, "释放redis分布式锁异常")
	// 	}
	// }
	// tx.Commit() // 需要自己手动提交操作
	return &emptypb.Empty{}, nil
}

func (s *Service) ReBack(_ context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
	// 库存归还
	// 1.订单超时需要归还之前扣减的库存(必须保证)
	// 2.订单创建失败需要归还之前扣减的库存(必须保证)
	// 3.用户点击取消手动归还库存
	// 因此这里也需要用到事务
	updater := mysql.InventoryUpdate{}
	updater.SetUpdatePolicy(mysql.NewReBack(s.DB))
	err := updater.Update(req.GoodsInfo)
	if err != nil {
		switch {
		case errors.Is(err, gorm.ErrRecordNotFound):
			return nil, status.Error(codes.NotFound, "没有库存信息")
		default:
			return nil, status.Error(codes.Internal, "归还失败")
		}
	}
	return &emptypb.Empty{}, nil
}
